C#中的Task物件其實可以視為一個Monad,今天要介紹Task的一些特性,並且幫它補上擴充方法。
觀察Task物件,我們可以知道兩件事情
我們可以將Task視為一個包裹了T的盒子,是不是有點像Monad了呢?接下來我們要來幫它實做Return/Map以及Bind
public static Task<T> Return<T>(T source) => Task.FromResult(source);
Return很簡單,當我們希望把一個值放到Task裡面的時候直接使用Task.FromResult
就好
public static async Task<TResult> Map<TSource,TResult>(this Task<TSource> source, Func<TSource,TResult> f)
=> f(await source);
當我們要取出Task裡面的值時,只要使用await就可以了,不過也因為使用到await的關係,Map需要加上async修飾詞。
public static async Task<TResult> Bind<TSource,TResult>(this Task<TSource> source, Func<TSource,Task<TResult>> f)
=> await f(await source);
注意一下Bind所接受的Func,需要回傳一個Task,所以除了要用await取得source內部的值以外,也需要取的整個Func的結果。
接下來我們可以檢查這樣設計是不是滿足Monad的條件
Left identity
var s = Return("Hello"); // Task<string> with "Hello"
s.Bind(x => Return(x)); // Task<string> with "Hello"
在Bind中我們會先取得x=”Hello”
,然後再次將Hello放入Task中。
Right identity
var s = Return("Hello");
s.Bind(x => Return(x+" World")); // Task<string> with "Hello World"
很顯然也滿足第二條
Associativity
var s = Return("Hello");
s.Bind(x => Return(x + " World"))
.Bind(x => Return(x + "!")); // Hello World!
s.Bind(x => Return(x + await Return(" World" + "!"))); // Hello World!
這樣代表也滿足了結合律
Task具有Monad的特性,明天我們來討論如何利用這樣的特性去設計非同步的程式。